/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using STSdb.General.Streams;
using STSdb.Files;
using STSdb.General.Buffers;

namespace STSdb.Volumes
{
    /// <summary>
    /// Class is ThreadSafe
    /// </summary>
    public class Volume : IVolume
    {
        private readonly OptimizedFileStream stream;
        private bool disposed = false;
        private const ulong HEADER_SIZE = 2 * Sector.SIZE;//2 - Reserved
        private const ulong DATA_OFFSET = HEADER_SIZE;

        private static readonly byte[] LOGO = Encoding.ASCII.GetBytes("STS Soft");

        public ulong FromByte { get; private set; }
        public ulong ToByte { get; private set; }

        public Volume(string fileName)
        {
            //stream = new OptimizedFileStream(fileName, FileMode.Open);
            stream = new OptimizedFileStream(fileName, FileMode.Open,FileAccess.ReadWrite,FileShare.None);

            ReadHeader();
        }

        public Volume(string fileName, ulong offset, ulong quota)
        {
            //stream = new OptimizedFileStream(fileName, FileMode.Create);
            stream = new OptimizedFileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.None);

            FromByte = offset;
            ToByte = FromByte + quota - 1;

            WriteHeader();
            Flush();
        }

        #region Header

        private void WriteHeader()
        {
            byte[] buffer = new byte[HEADER_SIZE];
            using (MemoryStream ms = new MemoryStream(buffer))
            {
                BinaryWriter writer = new BinaryWriter(ms);
                writer.Write(default(uint));//reserved for crc
                writer.Write((ushort)1);//version
                writer.Write(LOGO);
                writer.Write(FromByte);
                writer.Write(ToByte);

                //CRC
                ms.Seek(0, SeekOrigin.Begin);
                Crc32 crc = new Crc32();
                crc.Update(buffer, Crc32.SIZE, (int)(HEADER_SIZE - Crc32.SIZE));
                writer.Write(crc.Value);
            }

            stream.Seek(0, SeekOrigin.Begin);
            stream.Write(buffer, 0, (int)HEADER_SIZE);
        }

        private void ReadHeader()
        {
            byte[] buffer = new byte[HEADER_SIZE];
            stream.Seek(0, SeekOrigin.Begin);
            int readedBytes = stream.Read(buffer, 0, (int)HEADER_SIZE);
            Debug.Assert(readedBytes == (int)HEADER_SIZE);

            using (MemoryStream ms = new MemoryStream(buffer))
            {
                BinaryReader reader = new BinaryReader(ms);
                //CRC
                uint crcValue = reader.ReadUInt32();
                Crc32 crc = new Crc32();
                crc.Update(buffer, Crc32.SIZE, (int)(HEADER_SIZE - Crc32.SIZE));
                if (crc.Value != crcValue)
                    throw new Exception("Header CRC error!");

                ushort version = reader.ReadUInt16();
                switch (version)
                {
                    case 1:
                        break;
                    default:
                        throw new Exception("Invalid version.");
                }

                byte[] logo = reader.ReadBytes(LOGO.Length);
                if (Encoding.ASCII.GetString(logo) != Encoding.ASCII.GetString(LOGO))
                    throw new Exception("Unknown format.");

                FromByte = reader.ReadUInt64();
                ToByte = reader.ReadUInt64();
            }
        }

        #endregion

        #region IFile Members

        public void Write(ulong position, byte[] buffer, int index, int count)
        {
            long offset = checked((long)(DATA_OFFSET + position));
            lock (stream)
            {
                stream.Seek(offset, SeekOrigin.Begin);
                stream.Write(buffer, index, count);
            }
        }

        public void Read(ulong position, byte[] buffer, int index, int count)
        {
            long offset = checked((long)(DATA_OFFSET + position));
            lock (stream)
            {
                stream.Seek(offset, SeekOrigin.Begin);
                stream.Read(buffer, index, count);
            }
        }

        public void Flush()
        {
            lock (stream)
                stream.Flush();
        }

        public void Close()
        {
            lock (stream)
                stream.Close();
        }

        public ulong Length
        {
            get
            {
                lock (stream)
                    return (ulong)stream.Length - DATA_OFFSET;
            }
        }

        public ulong Quota
        {
            get { return ToByte - FromByte + 1; }
        }

        #endregion
    }
}
